package stud.mpgame.network;

import flash.Lib;

import flash.system.Security;

import flash.errors.SecurityError;

import flash.net.Socket;

import flash.utils.ByteArray;

import flash.events.Event;
import flash.events.ProgressEvent;

import stud.mpgame.network.MessageDispatcher;

import stud.mpgame.network.util.ResponseMessageDescription;

import stud.mpgame.network.service.time.TimeService;

import stud.mpgame.network.service.ping.PingNotificationMessageDescription;
import stud.mpgame.network.service.ping.PingRequestMessageDescription;

/**
 * ...
 * @author Timofeev
 */

class NetworkMonitor {

    private var sessionOpenedListeners:List< Dynamic -> Void >;
    private var sessionClosedListeners:List< Dynamic -> Void >;
    private var protocolVersion:Int;
    private var socket:Socket;
    private var messageDispatcher:MessageDispatcher;
	private var responseMessageDispatcher:MessageDispatcher;
    private static var serialNumberCnt:Int = 0;    
    private var messageLength:Int;
    private var properties:Hash< Dynamic >;
	
	private var timeService:TimeService; //wrong?
    
    public function new(protocolVersion:Int) {        
        properties = new Hash< Dynamic >();
        sessionOpenedListeners = new List<Dynamic -> Void>();
        sessionClosedListeners = new List<Dynamic -> Void>();
        this.messageDispatcher = new MessageDispatcher(this);
		this.responseMessageDispatcher = new MessageDispatcher(this);
        this.protocolVersion = protocolVersion;
        this.messageLength = -1;
		
		//register necessary descriptions
        MessageDescription.registerDescription(ResponseMessageDescription.getInstance());
        MessageDescription.registerDescription(PingNotificationMessageDescription.getInstance());
        MessageDescription.registerDescription(PingRequestMessageDescription.getInstance());
		
		timeService = new TimeService(this); //wrong? 
    }

    public function getProtocolVersion():Int {
        return protocolVersion;
    }
    
    public function getMessageDispatcher():IMessageDispatcher {
        return messageDispatcher;
    }
	
    public function getResponseMessageDispatcher():IMessageDispatcher {
        return responseMessageDispatcher;
    }	
	
	public function getTimeService():TimeService {
		return timeService;
	}

    public function bind(host:String, port:Int, policyPort:Int):Void {
        Security.loadPolicyFile("xmlsocket://" + host + ":" + policyPort);        
        socket = new Socket();
        socket.connect(host, port);        
        socket.addEventListener(Event.CONNECT, notifySessionOpened);
        socket.addEventListener(Event.CLOSE, notifySessionClosed);
        socket.addEventListener(ProgressEvent.SOCKET_DATA, decode);
    }
   
    public function addSessionOpenedListener(listener:Dynamic -> Void):Void {        
        sessionOpenedListeners.add(listener);
    }

    public function removeSessionOpenedListener(listener:Dynamic -> Void):Void {
        sessionOpenedListeners.remove(listener);
    }
    
    public function addSessionClosedListener(listener:Dynamic -> Void):Void {        
        sessionClosedListeners.add(listener);
    }

    public function removeSessionClosedListener(listener:Dynamic -> Void):Void {
        sessionClosedListeners.remove(listener);
    }

    public function notifySessionOpened(e:Event):Void {
        for (listener in sessionOpenedListeners) {
            listener(e);
        }
    }

    public function notifySessionClosed(e:Event):Void {
        for (listener in sessionClosedListeners) {
            listener(e);
        }
    }
    
    public function encode(message:Message):Void {
        var buffer:ByteArray = new ByteArray();
        var sentTime:Int = Lib.getTimer();
        message.writeExternal(buffer, serialNumberCnt++, sentTime, protocolVersion);
        socket.writeUnsignedInt(buffer.length);                
        buffer.position = 0;
        socket.writeBytes(buffer);
        socket.flush();
        messageDispatcher.messageSent(message);
		if (message.getMessageDescription().equals(ResponseMessageDescription.getInstance()))
			responseMessageDispatcher.messageSent(message);		
    }
    
    public function decode(e:Event):Void {
        if (messageLength == -1 && socket.bytesAvailable >= 4) {
            messageLength = socket.readInt();
        }
        if (messageLength <= 0 ) {
            throw new SecurityError("Received message length <= 0");
            socket.close();
        }
        if (messageLength > 0 && Std.int(socket.bytesAvailable) >= messageLength) {            
            var message:Message = new Message(this, null);
            var receivedTime:Int = Lib.getTimer();
            message.readExternal(socket, receivedTime);
            if (message.getProtocolVersion() != protocolVersion) {
                throw new SecurityError("Wrong protocol version");
            }
            messageDispatcher.messageReceived(message);
			if (message.getMessageDescription().equals(ResponseMessageDescription.getInstance()))
				responseMessageDispatcher.messageReceived(message);
            messageLength = -1;
            if (socket.bytesAvailable >= 4) {
                decode(e);
            }
        }
    }
    
    public function setProperty(propertyKey:String, propertyValue:Dynamic):Void {        
        properties.set(propertyKey, propertyValue);        
    }
    
    public function getProperty(propertyKey:String):Dynamic {
        return properties.get(propertyKey);
    }
    
    public function removeProperty(propertyKey:String):Void {
        properties.remove(propertyKey);
    }
    
}